package com.issue.common.utils.excel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.issue.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
@Slf4j
public class EasyExcelUtil {

    /**
     * 导出 Excel
     *
     * @param response    /
     * @param pageList    需要导出Excel的数据
     * @param exportClass 导出 Excel 转换的数据类型
     * @param fileName    导出的 Excel 文件名称
     * @throws Exception /
     */
    public static <T> void exportExcel(HttpServletResponse response, List<T> pageList, Class exportClass, String fileName) throws Exception {
        exportExcel(response, pageList, exportClass, fileName, null, 0);
    }

    /**
     * 自动对数据进行分 sheet 输出
     *
     * @param response          /
     * @param pageList
     * @param exportClass
     * @param fileName
     * @param otherWriteHandler
     * @param headRow
     * @param <T>
     * @throws Exception
     */
    public static <T> void exportExcel(HttpServletResponse response, List<T> pageList, Class exportClass,
                                       String fileName, WriteHandler otherWriteHandler, int headRow) throws Exception {


        List<SheetExport<T>> sheetList = new ArrayList<>();

        // 超过限制的数据分多sheet导出，单sheet最多支持1048576行
        if (!CollectionUtils.isEmpty(pageList)) {
            // sheet 数
            int countAll = pageList.size();
            int batchNum = Constants.Excel.SINGLE_SHEET_COUNT;
            int frequency = (countAll + batchNum - 1) / batchNum;
            List<List<T>> splitList = Stream.iterate(0, n -> n + 1)
                    .limit(frequency)
                    .parallel()
                    .map(a -> pageList.stream()
                            .skip(a * batchNum)
                            .limit(batchNum)
                            .parallel()
                            .collect(Collectors.toList())
                    )
                    .collect(Collectors.toList());

            int index = 1;
            for (List<T> dataList : splitList) {
                SheetExport sheetExport = new SheetExport<T>();
                sheetExport.exportClass = exportClass;
                sheetExport.headRow = headRow;
                sheetExport.data = dataList;
                sheetExport.name = fileName + "" + index;
                sheetList.add(sheetExport);
                index++;
            }
        }

        exportExcel(response, otherWriteHandler, sheetList, fileName);
    }

    public static <T> void exportExcel(HttpServletResponse response,
                                       WriteHandler otherWriteHandler,
                                       List<SheetExport<T>> sheetList,
                                       String fileName) throws IOException {
        // 产生头部和内容样式
        HorizontalCellStyleStrategy headWriteHandler = generateHeadWriter();
        // 初始化报文
        initResponse(response, otherWriteHandler, fileName);
        // 初始化 ExcelWriter
        ExcelWriter excelWriter = generateExcelWriter(response, otherWriteHandler, headWriteHandler);
        // 开始输出
        for (int i = 0; i < sheetList.size(); i++) {
            SheetExport sheetExport = sheetList.get(i);
            WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetExport.name)
                    .head(sheetExport.exportClass)
                    .relativeHeadRowIndex(sheetExport.headRow)
                    .build();
            excelWriter.write(sheetExport.data, writeSheet);
        }
        excelWriter.finish();
    }

    public static ExcelWriter generateExcelWriter(HttpServletResponse response, WriteHandler otherWriteHandler, HorizontalCellStyleStrategy headWriteHandler) throws IOException {
        ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream())
                .autoCloseStream(Boolean.FALSE)
                .registerWriteHandler(headWriteHandler);
        if (otherWriteHandler != null) {
            excelWriterBuilder.registerWriteHandler(otherWriteHandler);
        }
        return excelWriterBuilder.build();
    }

    public static void initResponse(HttpServletResponse response,
                                    WriteHandler otherWriteHandler,
                                    String fileName) throws IOException {
        response.setCharacterEncoding("utf-8");
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
        response.addHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
    }

    /**
     * 导入 Excel
     *
     * @param <T> 实体类类型
     * @return /
     */
    public static <T extends BaseImportVo> ResponseMsg importExcel(ImportParam<T> param) {
        if (param.getFile() == null) {
            return ResponseMsgUtil.error("缺少参数 file");
        }
        if (param.getImportBeanClazz() == null) {
            return ResponseMsgUtil.error("缺少参数 clazz");
        }
        if (param.getHandleListener() == null) {
            return ResponseMsgUtil.error("缺少 handle listener");
        }
        ResponseMsg comResp = new ResponseMsg<>();
        StringBuilder errorContent = new StringBuilder();
        String message = "";
        try {
            InputStream inputStream = param.getFile().getInputStream();
            List<ReadSheet> sheetList = EasyExcel.read(inputStream)
                    .excelType(param.getFileExtension())
                    .ignoreEmptyRow(true)
                    .build()
                    .excelExecutor()
                    .sheetList();
            inputStream.close();

            // 裁剪
            if (sheetList.size() > param.getSheetNumber()){
                sheetList = sheetList.subList(0, param.getSheetNumber());
            }

            ExcelImportListener<T> listener = new ExcelImportListener<>();
            // 总体是否错误变量
            boolean overAllValidFlag = true;
            for (ReadSheet readSheet : sheetList) {
                inputStream = param.getFile().getInputStream();
                try {
                    EasyExcel.read(inputStream, param.getImportBeanClazz(), listener)
                            .excelType(param.getFileExtension())
                            .ignoreEmptyRow(true)
                            .headRowNumber(param.getHeadNum())
                            .sheet(readSheet.getSheetNo())
                            .doRead();
                }catch (Exception e){
                    log.error("解析excel失败：" + e.getLocalizedMessage());
                    errorContent.append("解析excel失败：" + e.getLocalizedMessage());
                    overAllValidFlag = false;
                    continue;
                }
                List<T> excelData = listener.getData();
                boolean isValid = true;
                if (param.getValidator() != null) {
                    int position = param.getHeadNum();
                    for (T t : excelData) {
                        position++;
                        // 检测到一行数据有问题就直接返回，防止错误信息太长了
                        if (!param.getValidator().validate(t, errorContent, position)) {
                            isValid = false;
                            break;
                        }
                    }
                    if (!param.getValidator().validateAll(excelData, errorContent, param.getHeadNum() + 1)){
                        isValid = false;
                    }
                }
                if (!isValid) {
                    overAllValidFlag = false;
                }
                // 单 Sheet 校验通过之后才能插入数据，目前面向业务部门的导入功能都是单 Sheet 导入
                if (isValid && !CollectionUtils.isEmpty(excelData)) {
                    param.getHandleListener().handle(excelData, errorContent, param.getImportUserId());
                }
                listener.clearData();
                inputStream.close();
            }
            // 错误信息导出
            message = errorContent.toString();
            // 数据校验没问题
            if (overAllValidFlag) {
                if (StringUtils.isNotBlank(message)) {
                    comResp = ResponseMsgUtil.error("导入完成，错误信息如下：" + message);
                } else {
                    comResp = ResponseMsgUtil.success("导入完成");
                }
            }
            // 数据校验有问题
            else {
                comResp = ResponseMsgUtil.error(message);
            }
        } catch (Exception e) {
            comResp = ResponseMsgUtil.error(e.toString());
            e.printStackTrace();
        }
        return comResp;
    }

    /**
     * 下载模板，地址在 resources/file 下
     *
     * @param fileName 文件名称，如 a.xlsx
     */
    public static void downloadImportTemplate(HttpServletResponse response, HttpServletRequest request, String fileName) {
        response.setContentType(request.getServletContext().getMimeType(fileName));
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        InputStream in;
        OutputStream out;
        try {
            ClassPathResource classPathResource = new ClassPathResource("file/" + fileName);
            in = classPathResource.getInputStream();
            out = response.getOutputStream();
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            in.close();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成头部与内容样式
     *
     * @return
     */
    public static HorizontalCellStyleStrategy generateHeadWriter() {
        // 头部样式
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        WriteFont writeFont = new WriteFont();
        writeFont.setBold(true);
        writeFont.setFontHeightInPoints((short) 12);
        headWriteCellStyle.setWriteFont(writeFont);
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 内容样式
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }

    public static class SheetExport<T> {
        public String name;
        public int headRow;
        public Class exportClass;
        public List<T> data;
    }

}
